/*
 * Copyright (C) 2011 Young,Fey <fey.young@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package com.youngfey.sqlite
import groovy.util.logging.*
import groovy.transform.*
import java.sql.*
import groovy.sql.*

/**
 *
 * @author Young,Fey <fey.young@gmail.com>
 */
@Log
@Canonical
class SQLite {
    static { Class.forName('org.sqlite.JDBC') }
    private static verify = { File file ->
        assert file && file.exists() && file.isFile()
    }

    /**
     * 加载一个SQLite文件
     * @param file SQLite数据库文件，不得为NULL
     */
    static SQLite load(File file) {
        def sqlite = new SQLite(file)
        sqlite.load()
        sqlite
    }

    /**
     * @param file 数据库文件
     * @param tables 表集合
     * @param commit 是否在创建后立刻使用commit过程写入文件
     * 创建一个SQLite数据库
     */
    static SQLite create(File file, List<Table> tables = null, boolean commit = false) {
        if(!file.exists()) file.createNewFile()
        def sqlite = new SQLite(file)
        sqlite.addTables(tables) 
        if(commit) sqlite.commit()
        sqlite
    }

    private SQLite (File file) {
        verify file
        this.file = file
        log.fine "Create sqlite: $file.absolutePath"
    }

    /**
     * 添加一个表对象
     * 不得添加表名已存在的表
     */
    void addTable(Table table) {
        log.fine "Try to add table: $table"
        assert table, "Table could't be null"
        assert !getTables().containsKey(table.name), "Duplicated table: $table"
        table.sqlite = this
        tables.put(table.name, table)
    }

    /**
     * 添加一批表对象
     */
    void addTables(List<Table> tables) {
        tables.each { addTable it }
    }

    /**
     * 返回所有表，如tables为空，则尝试加载
     */
    Map<String, Table> getTables() {
        if(!tables) load()
        tables
    }

    /**
     * 直接对tables Map赋值，仅用于新建且未Commit的数据库
     */
    void setTables(Map<String, Table> tables) {
        assert tables, "Tables couldn't be null or empty"
        if(!getTables()) {
            log.warning "Try to set tables to existed database, ignored."
            return
        }
        this.tables = tables
        tables.each { k, v -> v.sqlite = this }
    }

    /**
     * 应用对数据库的更改
     */
    void commit() {
        tables.each {
            it.value.commit()
        }
        load()
        close()
    }

    /**
     * 以Map形式返回数据库所有的数据
     */
    Map toMap() {
        def map = [:]
        getTables().each { k, table ->
            map += table.toMap()
        }
        map
    }

    /**
     * 是否存在名为name的表定义（可能未commit)
     */
    boolean hasTable(String name) {
        tables.containsKey(name)
    }

    /**
     * 该数据库对象是否已经包含指定的表定义
     */
    boolean hasTable(Table table) {
        hasTable(table?.name)
    }

    /**
     * 数据库是否已包含名为name的表
     */
    boolean hasExistedTable(String name) {
        getSql().firstRow("""SELECT name FROM sqlite_master
                             WHERE type = 'table' AND name = $name""") 
    }

    /**
     * 便捷接口，同groovy.sql.Sql.eachRow
     */
    private eachRow(stmt, closure) {
        try {
            getSql().eachRow(stmt, closure)
        } catch(e) {
            log.severe "EachRow Exception: \nstmt\n$e"
            throw e
        }
    }

    /**
     * 便捷接口，同groovy.sql.Sql.execute
     */
    private execute(stmt) {
        try {
            getSql().execute(stmt)
        } catch(e) {
            log.severe "Execute Exception:\n$stmt\n$e"
            throw e
        }
    }

    /**
     * 便捷接口，同groovy.sql.Sql.dataSet
     */
    private dataSet(String tbl) {
        getSql().dataSet(tbl)
    }

    /**
     * 返回Connection对象以提供必要的JDBC支持
     */
    Connection getConnection() {
        if(!connection) {
            try {
                connection = DriverManager.getConnection("jdbc:sqlite:$file.absolutePath")
                connection.autoCommit = false
                log.config 'Get connection'
            } catch(e) {
                log.severe e
                throw e
            }
        }
        connection
    }

    /**
     * 返回Sql对象以便于在Groovy环境下进行JDBC操作
     */
    Sql getSql() {
        if(!sql) {
            try {
                sql = new Sql(getConnection())
            } catch(e) {
                log.severe e
                throw e
            }
        }
        sql
    }


    //从数据库文件加载数据（当前数据将被抛弃，未写入的数据将丢失）
    private load() {
        log.fine "Try to load tables"
        tables = [:]
        eachRow("""
            SELECT name, sql FROM sqlite_master
                WHERE type = 'table' AND name <> 'sqlite_sequence'
        """) { ts ->
            this.@tables.put(ts.name, new Table(ts.name, this, ts.sql, true))
        }
        log.fine "loaded ${tables.size()} tables:\n${tables.keySet()}"
        close()
    }

    /*
    private void setAutoCommit(boolean flag) {
        getConnection().autoCommit = flag
    }
*/
    private void close() {
        try {
            this.@sql?.close()
            this.@connection?.close()
            sql = null
            connection = null
        } catch(e) {
            log.warning e
        }
    }


    final File file 
    Map<String, Table> tables
    private connection, sql
}

